#ifndef __EMULATOR_H__
#define __EMULATOR_H__

#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>

/*	Emulator back-end API to be used by emulator front-end */

/*	Constants */
#define PHYSICAL_ADDRESS_BITS 19
#define MEMORY_SIZE (1<<PHYSICAL_ADDRESS_BITS)

/*	The opaque machine structure represents a machine at a
	given point in time. The state of the processor itself
	(excluding memory) is defined publicly, and available
	through the API below. */
struct machine;	
typedef struct machine *machine_t;
struct __attribute__((packed)) state {
    /*  A snapshot is a view of the processor at
        a given point in time. A snapshot is created
        by calling edit_state(), which clones the
        most recent state into an editable form. A
        snapshot can be modified manually (for example,
        by the front end), or can be updated by the
        tick() instruction. In addition to containing
        the absolute time and the state of the processor
        at that time, a snapshot contains a record of
        what write, if any, happened during that time.

        We store an array of SNAPSHOT_COUNT snapshots,
        enough to represent a second or so of execution
        at target clock speeds. Within this time period,
        we can then scrub backwards, allowing a wtf mode
        for debugging purposes. The indexes first_snapshot
        and last_snapshot keep track of what portion of
        the array is valid, and are managed collectively
        by initialize_state(), edit_state(), and untick().
        This is a circular array; if last_snapshot <
        first_snapshot, then snapshots wrap around at the
        top of the array. If last_snapshot == first_snapshot,
        the array is empty. */

    /* clock cycles since world started */
    uint64_t T : 40;    //absolute time in cycles
	bool	 S : 1;		//is this state saved?

    /* this is the write info */
    bool     w : 1;     //did a write happen at this time
    int32_t  a : PHYSICAL_ADDRESS_BITS; //where?
    int16_t  v : 16;    //value written

    /* below this point is processor state */
    bool     x : 1,     //execute or fetch?
             u : 1,     //user mode
             f : 1;     //flag bit
	#define REGISTER(n) STATE._r[(n) == PC && STATE.u ? UPC : (n)]
    int16_t  _r[8];      //registers; access through REGISTER macro
    int16_t  t[4];      //tlb entries
    int16_t  i;         //instruction buffer
	uint16_t e : 6,		//exception state
			 s : 8;		//serial port buffer
};

/*	Machine manipulation
	These operations act on an entire machine. A machine is
	created by calling create_machine(), and cleaned up via
	destroy_machine(). All other API functions require a
	machine to act on. save_machine() sticks a machine into the
	given buffer, and returns the bytes saved; NULL for the
	buffer will supply the number of bytes needed. load_machine()
	returns a new machine (or NULL on failure) that must be
	deallocated with destroy_machine() when done. */
enum filetype { FILETYPE_SMALL, FILETYPE_COMPLETE, FILETYPE_ASCII }; 
machine_t create_machine();
void destroy_machine(machine_t);
size_t save_machine(machine_t, enum filetype, void *buffer, size_t length);
machine_t load_machine(enum filetype, void *buffer, size_t length);

/*	Processor state
	The basic abstraction of the processor is that of its
	current state. Before the processor is started, or when
	it is to be restarted, the state must be reset via the
	initialize_state() call. (A newly created machine is
	automatically initialized.) Once this state has been created,
	it can be peeked at via get_state(). get_state() returns
	a pointer to the current state. This state is only valid
	until the next emulator API call is made, after which it
	must be re-acquired. The front-end may change the state
	directly, but only under the condition that it call
	edit_state() before each change, and make at most a single
	modification to the state returned by get_state() before
	the next call to edit_state(). In exchange for the front-
	end obeying this constraint, the function undo() can be
	called to roll back the last operation performed by the
	front-end in this manner. can_undo() indicates whether
	this operation will do anything.  */
void initialize_state(machine_t);
struct state *get_state(machine_t);
void edit_state(machine_t);
bool can_undo(machine_t);
void undo(machine_t);

/*	Interface to memory
	The processor has a 19-bit physical address space.
	Virtual addreses must be converted to physical addresses
	via v_to_p() before lookup can occur. v_to_p() will set
	the out argument error to true in the case that the virtual
	address given has no current physical mapping in the mode
	given (if read is true, then read mode; otherwise write).
	If the update flag is set, the used and dirty bits will be
	updated appropriately; the front-end should refrain from
	setting this flag. Once a physical address is obtained,
	reads and writes can be performed. Because memory-mapped i/o
	is performed, reads and writes can both, in some circumstances,
	have side-effects.  Setting the mmio argument to false suppresses
	these side-effects. Reads will return the best value they can
	without invoking side-effects. Writes will return false if the
	write attempt was rejected due to the mmio flag being false.
	the p_to_v() function is not used by the emulator, but is
	made available so the front end can show the equivalent virtual
	address(es) for any physical address that has one. */
int32_t v_to_p(machine_t, int16_t addr, bool read, bool update, bool *error);
int16_t p_to_v(machine_t, int32_t paddr, bool read, bool *error);
int16_t p_mem_read(machine_t, int32_t paddr, bool mmio);
bool p_mem_write(machine_t, int32_t paddr, int16_t value, bool mmio);

/*	Running the emulator
	The emulator can be stepped forward one cycle at a time
	by invoking tick(). The emulator also tries to support
	stepping backwards one cycle at a time to a previous
	state. can_untick() indicates whether such backward steps
	are available, and untick() performs one if possible. */
void tick(machine_t);
bool can_untick(machine_t);
void untick(machine_t);

/*	Interacting with the machine
	The emulator can interact with the machine by signalling
	hardware interrupts, and by interacting with I/O devices.
	Because hardware interrupts will be internally wired, only
	certain uses are possible. As with all API functions that
	change the machine state, sending signals and serial data
	should be done after edit_state() is called. Receiving serial
	data is via a callback mechanism. The callback function is
	provided with the byte written by the machine and with an
	arbitrary pointer provided by the front-end at callback
	registration time. */
void cause_nmi(machine_t); //signals that an NMI occurred
void cause_rtc(machine_t); //signals that the RTC ticked
void send_serial_byte(machine_t, uint8_t byte); //send a byte
void recv_serial_bytes(machine_t, void (*callback)(uint8_t, void *), void*);

#endif
